/*--------------------------------------------------------------------------------------*\
**
** adv_subterranean_gate.cpp
**
** Heroes IV
** Copyright 2000, The 3DO Company
**
\*--------------------------------------------------------------------------------------*/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )
#include "adv_subterranean_gate.h"

#include <limits>

#include "adv_object_type.h"
#include "adv_subterranean_gate_vector.h"
#include "adventure_frame.h"
#include "adventure_map.h"
#include "adventure_path_finder.h"
#include "adv_object_type.h"
#include "adv_actor_model.h"
#include "army.h"
#include "bitmap_group_cache.h"
#include "object_registration.h"
#include "player.h"
#include "random.h"
#include "simple_dialog.h"
#include "square.h"
#include "stationary_adventure_object.h"

// --------------------------------------------------------------------------
// Unnamed namespace
// --------------------------------------------------------------------------

namespace
{
	int const k_save_version = 2;

	t_object_registration< t_adv_subterranean_gate > g_adv_subterranean_gate_registration( k_adv_object_subterranean_gate );

	// Translate t_adv_subterranean_gate's save file version to t_gateway_base's save file version
	int to_gateway_base_save_version( int version )
	{
		if ( version == 0 )
			return 0;
		else
			return 1;
	}

} // Unnamed namespace

t_adv_subterranean_gate::t_adv_subterranean_gate ( std::string const& model_name )
          : t_gateway_base( model_name )
{
}

t_adv_subterranean_gate::~t_adv_subterranean_gate()
{
}


void t_adv_subterranean_gate::activate_trigger( t_army* army, t_adv_map_point const& point, 
		                                        t_direction direction, t_adventure_frame* frame )
{
	if (army->get_owner() == NULL) 
		return;

	if ( m_other_gate.get() != NULL )
	{
		/*
		** Teleport army.
		*/
		teleport_army ( *m_other_gate, army, frame, true, get_text( "initial" ) );
	}
	else
	{
		ok_dialog( get_text( "denied" ), true, get_name() );
	}
}

namespace
{
	// For use by t_adv_subterranean_gate
	struct t_gate_pair
	{
		t_gate_pair( t_adv_subterranean_gate_ptr upper_gate, t_adv_subterranean_gate_ptr lower_gate );

		int m_distance_squared;
		t_adv_subterranean_gate_ptr m_upper_gate;
		t_adv_subterranean_gate_ptr m_lower_gate;

	};

	inline t_gate_pair::t_gate_pair( t_adv_subterranean_gate_ptr upper_gate, t_adv_subterranean_gate_ptr lower_gate )
		: m_upper_gate( upper_gate), m_lower_gate( lower_gate )
	{
		m_distance_squared = distance_squared( upper_gate->get_position() - lower_gate->get_position() );
	}

	// An abitrary but *completely determined* ordering. No dependance on the order objects are in
	// the file, location of memory blocks, or how std::sort chooses to sort stuff. Ensure that 
	// loading the same map file will ALWAYS give the same ordering and thus the same pairs
	bool operator<( t_gate_pair const & pair1, t_gate_pair const & pair2 )
	{
		if ( pair1.m_distance_squared < pair2.m_distance_squared )
			return true;
		else if ( pair1.m_distance_squared > pair2.m_distance_squared )
			return false;

		t_map_point_2d point1 = pair1.m_upper_gate->get_position();
		t_map_point_2d point2 = pair2.m_upper_gate->get_position();

		if ( point1.row < point2.row )
			return true;
		else if ( point1.row > point2.row )
			return false;
	
		if ( point1.column < point2.column )
			return true;
		else if ( point1.column > point2.column )
			return false;

		point1 = pair1.m_lower_gate->get_position();
		point2 = pair2.m_lower_gate->get_position();

		if ( point1.row < point2.row )
			return true;
		else if ( point1.row > point2.row )
			return false;
	
		if ( point1.column < point2.column )
			return true;
		else if ( point1.column > point2.column )
			return false;

		return false;
	}

	typedef std::vector< t_gate_pair > t_gate_pair_vector;
}

// --------------------------------------------------------------------------
// Pair up all the subterrean gates & set up m_other_gate on all gates
// Static function
// --------------------------------------------------------------------------
void t_adv_subterranean_gate::calculate_pairs( t_adventure_map * map )
{

	t_adv_subterranean_gate_vector const & gates = map->get_subterranean_gates();
	t_adv_subterranean_gate_vector::const_iterator it_upper, it_lower;
	
	t_gate_pair_vector pair_vector;
	pair_vector.reserve( square( gates.size() / 2 ) );

	for ( it_upper = gates.begin(); it_upper != gates.end(); it_upper++ )
	{
		(*it_upper)->m_other_gate = NULL; // Used to indicate "match not found yet"

		if ( (*it_upper)->get_position().level != 0 )
			continue; // Not upper level

		for ( it_lower = gates.begin(); it_lower != gates.end(); it_lower++ )
		{
			if ( (*it_lower)->get_position().level != 1 )
				continue; // Not lower level

			pair_vector.push_back( t_gate_pair( *it_upper, *it_lower ) ) ;
		}
	}

	// Sort. Start with best matches first
	std::sort( pair_vector.begin(), pair_vector.end() );

	t_gate_pair_vector::iterator it_pair;

	for ( it_pair = pair_vector.begin(); it_pair != pair_vector.end(); it_pair++ )
	{
		if (   it_pair->m_upper_gate->m_other_gate.get() == NULL 
			&& it_pair->m_lower_gate->m_other_gate.get() == NULL )
		{
			// Not yet matched up
			it_pair->m_upper_gate->m_other_gate = it_pair->m_lower_gate;
			it_pair->m_lower_gate->m_other_gate = it_pair->m_upper_gate;
		}
	}
}


void t_adv_subterranean_gate::destroy()
{
	m_map->remove_subterranean_gate( this );

	t_gateway_base::destroy();

	// Kill link to this gate
	if ( m_other_gate.get() != NULL )
	{
		assert( m_other_gate->m_other_gate.get() == this );
		m_other_gate->m_other_gate = NULL;
	}
}

int t_adv_subterranean_gate::get_version() const
{
	return k_save_version;
}

// -------------------------------------------------------------------
// Called from ~t_adventure_map; last chance to clean up
// -------------------------------------------------------------------
void t_adv_subterranean_gate::on_adventure_map_destruction()
{
	// Break circular links to avoid memory leaks
	m_other_gate = NULL;
}

void t_adv_subterranean_gate::place( t_adventure_map& map, t_adv_map_point const& point )
{
    t_gateway_base::place( map, point );
    
    // Add gateway.
    map.add_subterranean_gate ( this );
}

void t_adv_subterranean_gate::pathing_destination_query( t_adventure_path_point const& source, 
														 t_adventure_path_finder & path_finder) const
{
	if ( m_other_gate.get() != NULL )
	{
		t_adv_map_point exit;
		if ( m_other_gate->find_adjacent_space( exit, *path_finder.get_army() ) )
		{
			exit.on_bridge = false;

			path_finder.push( exit, source, 0 );
		}
	}
}

bool t_adv_subterranean_gate::read( std::streambuf&						stream, 
									t_qualified_adv_object_type const&	type,
									int									version )
{
	if ( !t_gateway_base::read( stream, type, to_gateway_base_save_version( version ) ) )
		return false;

	if ( version >= 2 )
	{
		m_other_gate_id = read_adventure_global_id( stream );
	}
	else
	{
		m_other_gate_id = k_invalid_global_id;
	}

	return true;
}

void t_adv_subterranean_gate::read_postplacement( t_adventure_map& map )
{
	// Resolve temporary m_other_gate_id into permenant m_other_gate
	if ( m_other_gate_id == k_invalid_global_id )
	{
		m_other_gate = NULL;
	}
	else
	{
		t_adventure_object * object = map.get_object_by_global_id( m_other_gate_id );
		m_other_gate = dynamic_cast< t_adv_subterranean_gate * >( object );

		assert( m_other_gate.get() != NULL && m_other_gate->m_other_gate_id == get_global_id() );
	}
}

bool t_adv_subterranean_gate::write( std::streambuf & stream ) const
{
	if ( !t_gateway_base::write( stream ) )
		return false;

	if ( m_other_gate.get() == NULL )
	{
		write_adventure_global_id( stream, k_invalid_global_id );
	}
	else
	{
		write_adventure_global_id( stream, m_other_gate->get_global_id() );
	}

	return true;
}
